﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Fleck;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Microsoft.ClearScript.V8;

namespace ICU
{
    /// <summary>
    /// ICU Server
    /// Copyright (c) 2021, All rights reserved.
    /// 
    /// License:
    /// GPL v2
    /// https://gitee.com/zhaoliyan/ICU
    /// </summary>
    [DebuggerDisplay("{" + nameof(ToString) + "(),nq}")]
    public class Server : IServer
    {
#if DEBUG
        readonly bool debug = true;
#else
        readonly bool debug = false;
#endif
        Logger serverLogger;
        Random random = new Random();
        WebSocketServer webSocketServer;
        Dictionary<long, User> users = new Dictionary<long, User>();
        Dictionary<long, Group> groups = new Dictionary<long, Group>();
        Dictionary<long, IWebSocketConnection> ID2Connection = new Dictionary<long, IWebSocketConnection>();
        Dictionary<IWebSocketConnection, long> connection2ID = new Dictionary<IWebSocketConnection, long>();
        Dictionary<IWebSocketConnection, KeyValuePair<long, string>> registerTokens = new Dictionary<IWebSocketConnection, KeyValuePair<long, string>>();
        Dictionary<long, long> resetPasswordToken = new Dictionary<long, long>();
        Dictionary<long, KeyValuePair<Message, DateTime>> messageCache = new Dictionary<long, KeyValuePair<Message, DateTime>>();
        ConfigurationStructure config = new ConfigurationStructure();
        public override object configuration { get => config; protected set => config = (ConfigurationStructure)value; }
        Utils.NullableDictionary<IPAddress, int> packageCountIPAddress = new Utils.NullableDictionary<IPAddress, int>(0);
        Utils.NullableDictionary<IWebSocketConnection, int> packageCountConnection = new Utils.NullableDictionary<IWebSocketConnection, int>(0);
        Utils.NullableDictionary<long, int> uploadFileCount = new Utils.NullableDictionary<long, int>(0);
        DateTime ServerStart = DateTime.Now;
        long lastMessageID = 1;

        public override List<long> Banlist { get; protected set; }
        public override Dictionary<long, User> Users { get => users; }
        public override Dictionary<long, Group> Groups { get => groups; }
        public Dictionary<string, string> installTask = new Dictionary<string, string>();
        public bool IsRunning { get; protected set; } = false;

        public Server()
        {
            Console.WriteLine(
@" _____     _______    _      _ " + "\n" +
@"|__ __|   /  _____\  | |    | |" + "\n" +
@"  | |    /  /        | |    | |" + "\n" +
@"  | |   |  |         | |    | |" + "\n" +
@"  | |   |  |         | |    | |" + "\n" +
@"  | |   |  |         | |    | |" + "\n" +
@" _| |_   \  \______  \ \____/ /" + "\n" +
@"|_____|   \_______/   \______/ " + "\n" +
$"v{Properties.Defaults.Version}");
            Banlist = new List<long>();
            if (!Directory.Exists("config"))
            {
                Directory.CreateDirectory("config");
            }
            if (!Directory.Exists("data"))
            {
                Directory.CreateDirectory("data");
            }
            if (!Directory.Exists(@"data\face"))
            {
                Directory.CreateDirectory(@"data\face");
            }
            if (!Directory.Exists(@"data\groupface"))
            {
                Directory.CreateDirectory(@"data\groupface");
            }
            if (!Directory.Exists("message"))
            {
                Directory.CreateDirectory("message");
            }
            if (!Directory.Exists("plugin"))
            {
                Directory.CreateDirectory("plugin");
            }
            if (!Directory.Exists("usermessage"))
            {
                Directory.CreateDirectory("usermessage");
            }
            if (!Directory.Exists("logs"))
            {
                Directory.CreateDirectory("logs");
            }
            serverLogger = GetLogger("Server");
            if (File.Exists(@"data\common.json"))
            {
                JObject cfg = JObject.Parse(File.ReadAllText(@"data\common.json"));
                lastMessageID = cfg.Value<long>("mid");
            }
            if (File.Exists(@"config\server.json"))
            {
                config = JsonConvert.DeserializeObject<ConfigurationStructure>(File.ReadAllText(@"config\server.json"));
            }
            else
            {
                SaveConfig();
            }
            if (File.Exists(@"data\banlist.json"))
            {
                Banlist = JsonConvert.DeserializeObject<List<long>>(File.ReadAllText(@"data\banlist.json"));
            }
            if (File.Exists(@"data\user.json"))
            {
                foreach (var user in JsonConvert.DeserializeObject<List<User>>(File.ReadAllText(@"data\user.json")))
                {
                    users.Add(user.id, user);
                }
            }
            else
            {
                users.Add(0, new User { email = "icu@icu.com", id = 0, isOnlune = false, password = "", username = "系统消息" });
            }
            if (File.Exists(@"data\group.json"))
            {
                foreach (var group in JsonConvert.DeserializeObject<List<Group>>(File.ReadAllText(@"data\group.json")))
                {
                    groups.Add(group.id, group);
                }
            }
            foreach (var item in Directory.GetFiles("logs"))
            {
                if ((DateTime.Now - DateTime.Parse(Path.GetFileName(item).Remove(Path.GetFileName(item).LastIndexOf('-')))).Days >= config.logging.file_store_days)
                {
                    File.Delete(item);
                }
            }
            webSocketServer = new WebSocketServer((config.listen_any ? "ws://0.0.0.0:" : "ws://127.0.0.1:") + config.server_port);
        }
        public void NullCheck()
        {
            foreach (var item in GetType().GetRuntimeFields())
            {
                if (item.GetValue(this) == null)
                {
                    serverLogger.Error("Field check: {0} is null", item.Name);
                }
            }
            foreach (var item in GetType().GetProperties())
            {
                if (item.CanRead && item.GetValue(this) == null)
                {
                    serverLogger.Error("Properties check: {0} is null", item.Name);
                }
            }
        }
        public void Start()
        {
            IsRunning = true;
            serverLogger.Info("Loading plugins...");
            foreach (var asm in new DirectoryInfo("plugin").EnumerateFiles())
            {
                if (asm.Extension == ".dll")
                {
                    LoadComDllPlugin(asm.FullName);
                }
                else if (asm.Extension == ".js")
                {
                    LoadJavaScriptPlugin(asm.FullName);
                }
            }
            {
                string ret = "";
                foreach (var item in Plugins)
                {
                    ret += $"{item.Key}, ";
                }
                serverLogger.Info($"Done. Loaded plugins({Plugins.Count}): {ret}");
                WebClient webClient = new WebClient();
                if (!File.Exists("Updater.exe"))
                {
                    serverLogger.Error("Updater not found, downloading...");
                    try
                    {
                        webClient.DownloadFile("http://www.starlight.cool:13123/api/icu/Updater.exe", "Updater.exe");
                        serverLogger.Info("Finished!");
                    }
                    catch (WebException e)
                    {
                        serverLogger.Error($"Download failed: {e.Message}");
                    }
                }
                foreach (var item in Plugins.Values)
                {
                    if (item.UpdateUrl != null && item.UpdateUrl != "")
                    {
                        try
                        {
                            serverLogger.Info($"Checking update for {item.Name}");
                            Version web = Version.Parse(webClient.DownloadString($"{item.UpdateUrl}?type=check"));
                            if (web > item.Version)
                            {
                                try
                                {
                                    webClient.DownloadFile($"{item.UpdateUrl}?type=download", $"D_{item.Name}");
                                    installTask.Add(item.GetType().Assembly.Location, $"D_{item.Name}");
                                    serverLogger.Info($"There is a new version {web}, restarting to update");
                                    Console.OpenStandardInput().Write(Console.InputEncoding.GetBytes("restart\n"));
                                }
                                catch
                                {
                                    serverLogger.Warn($"There is a new version {web}, go to {item.UpdateUrl} to update");
                                }
                            }
                        }
                        catch (WebException e)
                        {
                            serverLogger.Error($"Checking update failed: {e.Message}");
                        }
                        catch (Exception e)
                        {
                            serverLogger.Error(e.ToString());
                        }
                    }
                }
            }
            onPluginEnable(new PluginEnableEventArgs());
            // auto save and file delete task
            Task.Run(async () =>
            {
                while (IsRunning)
                {
                    await Task.Delay(1000 * 300);
                    {
                        List<long> delid = new List<long>();
                        foreach (var i in messageCache)
                        {
                            if ((DateTime.Now - i.Value.Value).Seconds > config.message_cache_time)
                            {
                                delid.Add(i.Key);
                            }
                        }
                        foreach (var i in delid)
                        {
                            messageCache.Remove(i);
                        }
                        if (config.auto_save.enable)
                        {
                            Save(config.auto_save.log);
                        }
                    }
                }
            });
            Task.Run(async () =>
            {
                while (IsRunning)
                {
                    await Task.Delay((int)(1000 * config.anti_ddos.reset_time));
                    packageCountConnection.Clear();
                    packageCountIPAddress.Clear();
                }
            });
            Task.Run(async () =>
            {
                while (IsRunning)
                {
                    await Task.Delay(DateTime.Now.AddDays(1).Date - DateTime.Now);
                    uploadFileCount.Clear();

                }
            });
            Task.Run(async () =>
            {
                while (IsRunning && config.files.auto_delete)
                {
                    await Task.Delay(1000 * 3600);
                    for (long i = lastMessageID; i >= 1; i--)
                    {
                        Message message = GetMessage(i, false);
                        if (message.isFile && !message.isUsed && !message.isDeleted)
                        {
                            if ((DateTime.Now - message.sendTime).Days > config.files.file_store_time)
                            {
                                message.isDeleted = true;
                                WriteMessage(i, message);
                                File.Delete(Path.Combine("message", message.messageId.ToString()));
                            }
                        }
                    }
                }
            });
            webSocketServer.Start(new Action<IWebSocketConnection>(InitConnection));
            serverLogger.Info("Server started successfully. Name: {0}", ConsoleColor.Gray, config.server_name ?? "<null>");
            serverLogger.Info("Join key: {0}", ConsoleColor.Gray, config.join_key ?? "no join key");
            ServerStart = DateTime.Now;
        }

        private void LoadJavaScriptPlugin(string fullName)
        {
            try
            {
                V8ScriptEngine engine = new V8ScriptEngine();
                engine.AddHostObject("server", this);
                engine.Script.path = fullName;
                engine.Execute(File.ReadAllText(fullName));
                JavaScriptPlugin plugin = new JavaScriptPlugin(engine, this);
                Version.Parse("1.0.0");
                serverLogger.Info($"Found plugin '{plugin.Name}'v{plugin.Version} in {fullName}(JavaScript).");
                Plugins.Add(plugin.Name, plugin);
            }
            catch (Exception e)
            {
                serverLogger.Error("{0}", e);
                serverLogger.Info("Template script is here:\n{0}", ConsoleColor.Green, JavaScriptPlugin.TemplatePlugin);
            }
        }

        private void LoadComDllPlugin(string name)
        {
            try
            {
                if (!File.Exists(name))
                {
                    serverLogger.Error($"Plugin '{name}' does not exist.");
                    return;
                }
                Assembly assembly = Assembly.LoadFrom(name);
                Type[] types = assembly.GetExportedTypes();
                foreach (var type in types)
                {
                    if (typeof(Plugin).IsAssignableFrom(type))
                    {
                        Plugin plugin = (Plugin)Activator.CreateInstance(type, this, name);
                        if (plugin.Name.IndexOf(' ') != -1)
                        {
                            serverLogger.Error($"Plugin name '{plugin.Name}' cannot have space");
                            plugin.Dispose();
                            return;
                        }
                        if (Plugins.ContainsKey(plugin.Name))
                        {
                            serverLogger.Warn($"Plugin '{name}' has already existed.");
                            if (plugin.Version > Plugins[plugin.Name].Version)
                            {
                                serverLogger.Info($"Version is higher({plugin.Version} than {Plugins[plugin.Name].Version}), replace.");
                                Plugins[plugin.Name].Dispose();
                                Plugins[plugin.Name] = plugin;
                            }
                            return;
                        }
                        try
                        {
                            serverLogger.Info($"Found plugin '{plugin.Name}'v{plugin.Version} in {name}.");
                            Plugins.Add(plugin.Name, plugin);
                        }
                        catch (Exception e)
                        {
                            serverLogger.Error($"There's an exception in '{plugin.Name}'v{plugin.Version}:");
                            serverLogger.Error($"{e}");
                            Plugins.Remove(plugin.Name);
                            serverLogger.Info($"Unload '{plugin.Name}' finished");
                        }
                    }
                }
            }
            catch (Exception e)
            {
                serverLogger.Info($"There's an exception in '{name}':");
                serverLogger.Error($"{e}");
            }
        }
        protected virtual void InitConnection(IWebSocketConnection connection)
        {
            connection.OnOpen = new Action(() =>
            {
                if (config.anti_ddos.enable && config.anti_ddos.max_package_ip >= 0 && packageCountIPAddress[GetRealIPAddress(connection)] > config.anti_ddos.max_package_ip)
                {
                    connection.Close();
                }
                if (debug || config.log_package) Console.WriteLine(new JObject { { "open", "" }, { "client", connection.ConnectionInfo.ClientIpAddress + ":" + connection.ConnectionInfo.ClientPort } });
            });
            connection.OnClose = new Action(() =>
            {
                if (debug || config.log_package) Console.WriteLine(new JObject { { "close", "" }, { "client", connection.ConnectionInfo.ClientIpAddress + ":" + connection.ConnectionInfo.ClientPort } });
                if (connection2ID.ContainsKey(connection))
                {
                    onUserLogout(new UserLogoutEventArgs { Id = connection2ID[connection], cause = LogoutCause.Disconnected });
                    users[connection2ID[connection]].isOnlune = false;
                    ID2Connection.Remove(connection2ID[connection]);
                    connection2ID.Remove(connection);
                }
                if (registerTokens.ContainsKey(connection))
                {
                    registerTokens.Remove(connection);
                }
                connection.Close();
            });
            connection.OnError = new Action<Exception>(ex =>
            {
                serverLogger.Error("{0}", ex);
                if (ex is System.IO.IOException)
                {
                    serverLogger.Warn("IOException: Closing client...");
                    if (connection2ID.ContainsKey(connection))
                    {
                        onUserLogout(new UserLogoutEventArgs { Id = connection2ID[connection], cause = LogoutCause.Disconnected });
                        users[connection2ID[connection]].isOnlune = false;
                        ID2Connection.Remove(connection2ID[connection]);
                        connection2ID.Remove(connection);
                    }
                    if (registerTokens.ContainsKey(connection))
                    {
                        registerTokens.Remove(connection);
                    }
                    connection.Close();
                }
            });
            connection.OnBinary = new Action<byte[]>(buffer =>
            {
                if (config.anti_ddos.enable)
                {
                    if ((packageCountConnection[connection] > config.anti_ddos.max_package_connection && config.anti_ddos.max_package_connection >= 0)
                    || (packageCountIPAddress[GetRealIPAddress(connection)] > config.anti_ddos.max_package_ip && config.anti_ddos.max_package_ip >= 0))
                    {
                        Task.Run(async () =>
                        {
                            await SendErrorAsync(connection, Errors.Other_TooManyPacket);
                            connection.Close();
                        });
                        return;
                    }
                    else
                    {
                        packageCountConnection[connection] += 1;
                        packageCountIPAddress[GetRealIPAddress(connection)] += 1;
                    }
                }
                if (!connection2ID.ContainsKey(connection))
                {
                    _ = SendErrorAsync(connection, Errors.Other_NotLoggedin);
                    return;
                }
                int headerLength = buffer[0] * 256 + buffer[1];
                if (headerLength + 2 > buffer.Length)
                {
                    return;
                }
                buffer = buffer[(2 + headerLength)..];
                JObject header = JObject.Parse(Encoding.UTF8.GetString(buffer[2..(2 + headerLength)]));
                _ = header.Value<string>("buffer_type") switch
                {
                    "File" => onUploadFileAsync(connection2ID[connection], header, buffer, connection),
                    "Face" => onUploadFaceAsync(connection2ID[connection], header, buffer, connection),
                    "GroupFace" => onUploadGroupFaceAsync(connection2ID[connection], header, buffer, connection),
                    _ => SendErrorAsync(connection, Errors.Other_UnknownMethod)
                };
            });
            connection.OnMessage = new Action<string>(msg =>
            {
                if (config.anti_ddos.enable)
                {
                    if ((packageCountConnection[connection] > config.anti_ddos.max_package_connection && config.anti_ddos.max_package_connection >= 0)
                    || (packageCountIPAddress[GetRealIPAddress(connection)] > config.anti_ddos.max_package_ip && config.anti_ddos.max_package_ip >= 0))
                    {
                        Task.Run(async () =>
                        {
                            await SendErrorAsync(connection, Errors.Other_TooManyPacket);
                            connection.Close();
                        });
                        return;
                    }
                    else
                    {
                        packageCountConnection[connection] += 1;
                        packageCountIPAddress[GetRealIPAddress(connection)] += 1;
                    }
                }
                if (debug || config.log_package) Console.WriteLine(new JObject { { "msg", msg }, { "client", connection.ConnectionInfo.ClientIpAddress + ":" + connection.ConnectionInfo.ClientPort } });
                if (!msg.Contains("\n\n")) return;
                if (msg.StartsWith("test") && msg.Length <= 20) { connection.Send(msg); return; }
                string[] header = msg[..msg.IndexOf("\n\n")].Split('\n');
                msg = msg[(msg.IndexOf("\n\n") + 2)..];
                if (header[0] != "Login" && header[0] != "RestPassword" &&
                header[0] != "Register" && (!connection2ID.ContainsKey(connection)))
                {
                    _ = SendErrorAsync(connection, Errors.Other_NotLoggedin);
                    return;
                }
                _ = header[0] switch
                {
                    "Login" => onLoginAsync(header[1..], msg, connection),
                    "Register" => onRegisterAsync(header[1..], msg, connection),
                    "Logout" => onLogoutAsync(connection2ID[connection], msg, connection),
                    "Receive" => onClientReceiveMessageAsync(connection2ID[connection], header[1..], connection),
                    "ResetPassword" => onResetPasswordAsync(msg, connection),
                    "ServerInformation" => onClientRequestInformation(connection2ID[connection], msg, connection),
                    "AddGroup" => onAddGroupAsync(connection2ID[connection], msg, connection),
                    "DeleteGroupAsync" => onDeleteGroupAsync(connection2ID[connection], msg, connection),
                    "GiveGroup" => onGiveGroupAsync(connection2ID[connection], msg, connection),
                    "SetGroupAdmin" => onSetGroupAdminAsync(connection2ID[connection], msg, connection),
                    "JoinGroup" => onJoinGroupAsync(connection2ID[connection], msg, connection),
                    "LeaveGroup" => onLeaveGroupAsync(connection2ID[connection], msg, connection),
                    "AllowJoinGroupRequest" => onAllowJoinGroupRequestAsync(connection2ID[connection], msg, connection),
                    "SetGroupInfo" => onSetGroupInfoAsync(connection2ID[connection], msg, connection),
                    "KickGroupMember" => onKickGroupMemberAsync(connection2ID[connection], msg, connection),
                    "Send" => onClientSendMessageAsync(connection2ID[connection], msg, connection),
                    "Search" => onSearchInformationAsync(connection2ID[connection], msg, connection),
                    "Download" => onDownloadAsync(connection2ID[connection], Convert.ToInt64(msg), connection),
                    _ => SendErrorAsync(connection, Errors.Other_UnknownMethod),
                };
            });
        }
        protected IPAddress GetRealIPAddress(IWebSocketConnection connection) => IPAddress.Parse(config.anti_ddos.use_proxy ? connection.ConnectionInfo.Headers[config.anti_ddos.real_ip_header] : connection.ConnectionInfo.ClientIpAddress);
        protected async Task SendErrorAsync(IWebSocketConnection connection, Errors error)
        {
            await connection.Send("Error\n" + (int)error + "\n\n");
        }
        public void SendEmail(string to, string subject, string body)
        {
            bool qq = to.EndsWith("@qq.com");
            SmtpClient client = new SmtpClient(qq ? "smtp.qq.com" : "smtp.yeah.net");
            MailAddress from = new MailAddress(qq ? "icuserver@qq.com" : "starlightserver@yeah.net", "ICU Server", Encoding.UTF8);
            MailAddress to1 = new MailAddress(to, "", Encoding.UTF8);
            MailMessage message = new MailMessage(from, to1)
            {
                Subject = subject,
                SubjectEncoding = Encoding.UTF8,
                Body = body,
                BodyEncoding = Encoding.UTF8,
                IsBodyHtml = false
            };

            client.DeliveryMethod = SmtpDeliveryMethod.Network;
            client.EnableSsl = true;
            client.UseDefaultCredentials = false;
            string username = qq ? "icuserver" : "starlightserver";
            string passwd = qq ? "kzkrwnaukmjogcgi" : "NVGEYNJEOBBMAJFB";
            //用户登陆信息
            NetworkCredential myCredentials = new NetworkCredential(username, passwd);
            client.Credentials = myCredentials;
            //发送邮件
            client.SendAsync(message, null);
        }
        public static string SHA256(string str)
        {
            byte[] SHA256Data = Encoding.UTF8.GetBytes(str);
            SHA256Managed Sha256 = new SHA256Managed();
            byte[] by = Sha256.ComputeHash(SHA256Data);
            return BitConverter.ToString(by).Replace("-", ""); //64
        }
        public virtual Message GetMessage(long id, bool _InCache = true)
        {
            Message ret;
            if (messageCache.ContainsKey(id))
            {
                ret = messageCache[id].Key;
                messageCache[id] = new KeyValuePair<Message, DateTime>(ret, DateTime.Now);
            }
            else
            {
                ret = JsonConvert.DeserializeObject<Message>(File.ReadAllText($"message\\{id}.json"));
                if (_InCache)
                {
                    messageCache.Add(id, new KeyValuePair<Message, DateTime>(ret, DateTime.Now));
                }
            }
            return ret;
        }
        protected void WriteMessage(long id, Message message)
        {
            if (messageCache.ContainsKey(id))
            {
                messageCache[id] = new KeyValuePair<Message, DateTime>(message, DateTime.Now);
            }
            File.WriteAllText($"message\\{id}.json", JsonConvert.SerializeObject(message));
        }
        protected long GetMessageId() => ++lastMessageID;
        protected Message CreateMessage(long sender, long target, bool isGroup, string message, long reply)
        {
            return new Message
            {
                messageId = ++lastMessageID,
                fileName = "",
                isFile = false,
                filePath = "",
                isGroup = isGroup,
                target = target,
                sender = sender,
                isReply = reply != 0,
                replyId = reply,
                msg = message,
                isPhoto = false,
                sendTime = DateTime.Now
            };
        }
        protected Message CreateMessage(long sender, long target, bool isGroup, string filename, string filePath, bool isPhoto)
        {
            Message message = GetMessage(Convert.ToInt64(filePath), false);
            if (!message.isUsed)
            {
                message.isUsed = true;
                WriteMessage(message.messageId, message);
            }
            return new Message
            {
                messageId = ++lastMessageID,
                filePath = filePath,
                fileName = filename,
                isFile = true,
                isGroup = isGroup,
                target = target,
                sender = sender,
                isReply = false,
                replyId = 0,
                msg = isPhoto ? "Special:Photo" : "Special:File",
                isPhoto = isPhoto,
                sendTime = DateTime.Now
            };
        }
        protected virtual async Task SendMessageAsync(Message message)
        {
            message.msg = message.msg.Trim();
            if (message.msg == "")
            {
                return;
            }
            if (message.isGroup)
            {
                messageCache.Add(message.messageId, new KeyValuePair<Message, DateTime>(message, DateTime.Now));
                foreach (var i in groups[message.target].users)
                {
                    if (ID2Connection.ContainsKey(i))
                    {
                        _ = ID2Connection[i].Send("Receive\n\n" + JsonConvert.SerializeObject(message));
                    }
                    await File.AppendAllTextAsync(@"usermessage\" + i, message.messageId.ToString() + '\n');
                }
                await File.WriteAllTextAsync(@"message\" + message.messageId + ".json", JsonConvert.SerializeObject(message));
            }
            else
            {
                await File.AppendAllTextAsync(@"usermessage\" + message.target, message.messageId.ToString() + '\n');
                await File.AppendAllTextAsync(@"usermessage\" + message.sender, message.messageId.ToString() + '\n');
                await File.WriteAllTextAsync(@"message\" + message.messageId + ".json", JsonConvert.SerializeObject(message));
                _ = ID2Connection[message.sender].Send("Receive\n\n" + JsonConvert.SerializeObject(message));
                if (ID2Connection.ContainsKey(message.target))
                {
                    _ = ID2Connection[message.target].Send("Receive\n\n" + JsonConvert.SerializeObject(message));
                }
            }
        }
        protected byte[] GetBufferPackage(JObject jo, byte[] buffer)
        {
            byte[] a = Encoding.UTF8.GetBytes(jo.ToString(Formatting.None));
            byte[] ret = new byte[2 + a.Length + buffer.Length];
            ret[0] = (byte)(a.Length / 256);
            ret[1] = (byte)(a.Length % 256);
            for (int i = 0; i < a.Length; i++)
            {
                ret[i + 2] = a[i];
            }
            for (int i = 0; i < buffer.Length; i++)
            {
                ret[i + 2 + a.Length] = buffer[i];
            }
            return ret;
        }
        public long BinarySearch(IComparable[] list, IComparable target)
        {
            long a = 0, b = list.Length - 1;
            if (list[a].Equals(target)) return a;
            if (list[b].Equals(target)) return b;
            while (true)
            {
                long i = (a + b) / 2;
                if (target.CompareTo(list[i]) < 0)
                {
                    b = i;
                }
                else if (Math.Abs(a - b) <= 1)
                {
                    if (list[a].Equals(target)) return a;
                    else if (list[b].Equals(target)) return b;
                    else throw new Exception("Server.BinarySearch: Object not found");
                }
                else
                {
                    a = i;
                }
            }
        }
        protected async Task onResetPasswordAsync(string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (!obj.ContainsKey("id"))
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
                return;
            }
            long id = obj.Value<long>("id");
            if (!users.ContainsKey(id))
            {
                await SendErrorAsync(connection, Errors.ResetPassword_NoSuchUser);
                return;
            }
            if (resetPasswordToken.ContainsKey(id))
            {
                await connection.Send("ResetPassword\nEmailSent\n\n");
                return;
            }
            if (obj.ContainsKey("password") && obj.ContainsKey("token"))
            {
                if (!resetPasswordToken.ContainsKey(id) || obj.Value<long>("token") != resetPasswordToken[id])
                {
                    await SendErrorAsync(connection, Errors.ResetPassword_WrongToken);
                    return;
                }
                users[id].password = SHA256(obj.Value<string>("password"));
                await connection.Send("ResetPassword\nSuccess\n\n");
            }
            else
            {
                resetPasswordToken.Add(id, random.Next());
                SendEmail(users[id].email, "ICU密码重置验证码", string.Format("你的验证码是{0}，请勿告诉他人\n警告:这个验证码用于重置密码，告知他人将会严重危害您的账户安全。", resetPasswordToken[id]));
                _ = Task.Run(async () =>
                {
                    await Task.Delay((int)(config.token_keep_time * 1000));
                    resetPasswordToken.Remove(id);
                });
                await connection.Send("ResetPassword\nSendEmail\n\n");
            }
        }
        protected virtual async Task onDownloadAsync(long id, long mid, IWebSocketConnection connection)
        {
            Message message = GetMessage(mid);
            if (!message.isFile)
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
                return;
            }
            if ((message.isGroup && groups[message.target].users.Contains(id)) || ((!message.isGroup) && (message.target == id || message.sender == id)))
            {
                await connection.Send(GetBufferPackage(new JObject { { "type", "file" }, { "id", id } }, File.ReadAllBytes($"message\\{mid}")));
            }
            else
            {
                await SendErrorAsync(connection, Errors.Download_NoPermission);
            }
        }
        protected virtual async Task onUploadFaceAsync(long id, JObject header, byte[] buffer, IWebSocketConnection connection)
        {
            await File.WriteAllBytesAsync(@"data\face\" + id + ".jpg", buffer);
            await connection.Send("UploadFace\nSuccess\n\n");
        }
        protected virtual async Task onUploadGroupFaceAsync(long id, JObject header, byte[] buffer, IWebSocketConnection connection)
        {
            long gid = header.Value<long>("id");
            if (id == groups[gid].owner || groups[gid].admins.Contains(id))
            {
                await File.WriteAllBytesAsync(@"data\groupface\" + header.Value<long>("id") + ".jpg", buffer);
                await connection.Send("UploadGroupFace\nSuccess\n\n");
            }
            else
            {
                await SendErrorAsync(connection, Errors.Group_NoPermission);
            }
        }
        protected virtual async Task onUploadFileAsync(long id, JObject header, byte[] buffer, IWebSocketConnection connection)
        {
            if (header.ContainsKey("filename") &&
                header.ContainsKey("target") &&
                header.ContainsKey("isPhoto") &&
                header.ContainsKey("isGroup") &&
                header.ContainsKey("filename") &&
                header.ContainsKey("keep"))
            {
                Message message;
                if (header.ContainsKey("path"))
                {
                    string path = header.Value<string>("path");
                    message = CreateMessage(id, header.Value<long>("target"), header.Value<bool>("isGroup"), header.Value<string>("filename"), path, header.Value<bool>("isPhoto"));
                    if (Regex.Match(path, "[0-9]*$").Value != path || (!File.Exists(@"message\" + path)))
                    {
                        _ = SendErrorAsync(connection, Errors.Send_NoSuchFile);
                        return;
                    }
                }
                else
                {
                    if (buffer.Length > 1024 * config.files.photo_size_limit && header.Value<bool>("isPhoto"))
                    {
                        _ = SendErrorAsync(connection, Errors.Send_PhotoTooBig).ConfigureAwait(false);
                        return;
                    }
                    if (!header.Value<bool>("isPhoto"))
                    {
                        if (uploadFileCount[id] > config.files.max_file)
                        {
                            _ = SendErrorAsync(connection, Errors.Other_TooManyPacket);
                            return;
                        }
                        else
                        {
                            uploadFileCount[id] += 1;
                        }
                    }
                    message = CreateMessage(id, header.Value<long>("target"), header.Value<bool>("isGroup"), header.Value<string>("filename"), "", header.Value<bool>("isPhoto"));
                    message.filePath = message.messageId.ToString();
                    await File.WriteAllBytesAsync(@"message\" + message.messageId, buffer);
                }
                await SendMessageAsync(message);
                await connection.Send("UploadFile\nSuccess\n\n");
            }
        }
        protected virtual async Task onSearchInformationAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("target") && obj.ContainsKey("type"))
            {
                long target = obj.Value<long>("target");
                switch (obj.Value<string>("type"))
                {
                    case "face":
                        if (File.Exists(@"data\face\" + target + ".jpg"))
                        {
                            _ = connection.Send(GetBufferPackage(new JObject { { "type", "face" }, { "id", target } }, File.ReadAllBytes(@"data\face\" + target + ".jpg")));
                        }
                        else
                        {
                            byte[] buffer = new byte[1024 * 100];
                            int length = Properties.Defaults.ResourceManager.GetStream("defaultface").Read(buffer);
                            _ = connection.Send(GetBufferPackage(new JObject { { "type", "face" }, { "id", target } }, buffer[..length]));
                        }
                        break;
                    case "groupface":
                        if (File.Exists(@"data\groupface\" + target + ".jpg"))
                        {
                            _ = connection.Send(GetBufferPackage(new JObject { { "type", "groupface" }, { "id", target } }, File.ReadAllBytes(@"data\groupface\" + target + ".jpg")));
                        }
                        else
                        {
                            byte[] buffer = new byte[1024 * 100];
                            int length = Properties.Defaults.ResourceManager.GetStream("defaultface").Read(buffer);
                            _ = connection.Send(GetBufferPackage(new JObject { { "type", "groupface" }, { "id", target } }, buffer[..length]));
                        }
                        break;
                    case "groups":
                        await connection.Send("Search\ngroups\n\n" + JsonConvert.SerializeObject(users[id].groups));
                        break;
                    case "friend":
                        await connection.Send("Search\nfriend\n\n" + JsonConvert.SerializeObject(users[id].friends));
                        break;
                    case "userinfo":
                        await connection.Send("Search\nuserinfo\n\n" + JsonConvert.SerializeObject(users[target]));
                        break;
                    case "groupinfo":
                        if (groups[target].users.Contains(id))
                        {
                            await connection.Send("Search\ngroupinfo\n\n" + new JObject {
                                { "admins", JArray.FromObject(groups[target].admins)},
                                { "owner", groups[target].owner},
                                { "id", groups[target].id} ,
                                { "joinWay", (int)groups[target].joinWay},
                                { "joinQuestion", groups[target].joinQuestion},
                                { "name", groups[target].name},
                                { "users", JArray.FromObject(groups[target].users) }
                            }.ToString(Formatting.None));
                        }
                        else
                        {
                            await connection.Send("Search\ngroupinfo\n\n" + new JObject {
                                { "admins", JArray.FromObject(groups[target].admins)},
                                { "owner", groups[target].owner},
                                { "id", groups[target].id} ,
                                { "joinWay", (int)groups[target].joinWay},
                                { "joinQuestion", groups[target].joinQuestion},
                                { "name", groups[target].name}
                            }.ToString(Formatting.None));
                        }
                        break;
                    default:
                        await SendErrorAsync(connection, Errors.Other_UnknownMethod);
                        break;
                }
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onClientReceiveMessageAsync(long id, string[] header, IWebSocketConnection connection)
        {
            if (header.Length == 1)
            {
                string[] msgs = File.ReadAllLines(@"usermessage\" + id);
                if (header[0] == "0")
                {
                    for (long i = 0; i < msgs.Length; i++)
                    {
                        long mid = Convert.ToInt64(msgs[i]);
                        await connection.Send("Receive\n\n" + JsonConvert.SerializeObject(GetMessage(mid)));
                    }
                }
                else
                {
                    long index = BinarySearch(msgs, header[0]);
                    for (long i = index + 1; i < msgs.Length; i++)
                    {
                        long mid = Convert.ToInt64(msgs[i]);
                        await connection.Send("Receive\n\n" + JsonConvert.SerializeObject(GetMessage(mid)));
                    }
                }
            }
        }
        protected virtual async Task onClientRequestInformation(long id, string msg, IWebSocketConnection connection)
        {
            await connection.Send("ServerInformation\n\n" +
                new JObject
                {
                    {"name",config.server_name },
                    {"running_time", (DateTime.Now-ServerStart).TotalSeconds},
                    {"file_limits",JObject.FromObject(config.files) },
                    {"packet_limits", new JObject{ { "connection", config.anti_ddos.max_package_connection },{ "ip", config.anti_ddos.max_package_ip },{ "enable", config.anti_ddos.enable },{ "reset", config.anti_ddos.reset_time } } }
                }
                .ToString(Formatting.None));
        }
        protected virtual async Task onAddGroupAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("name"))
            {
                long gid;
                for (gid = random.Next(); groups.ContainsKey(gid); gid = random.Next()) { }
                Group group = new Group(id, gid, obj.Value<string>("name"));
                groups.Add(gid, group);
                users[id].groups.Add(gid);
                await connection.Send("AddGroup\nSuccess\n\n" + gid);
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onDeleteGroupAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("gid"))
            {
                long gid = obj.Value<long>("gid");
                if (groups[gid].owner != id)
                {
                    await SendErrorAsync(connection, Errors.Group_NoPermission);
                    return;
                }
                else
                {
                    foreach (long u in groups[gid].users)
                    {
                        await SendMessageAsync(CreateMessage(0, u, false, string.Format("群[{0}({1})]已被群主解散", groups[gid].name, gid), 0));
                    }
                    groups.Remove(gid);
                    await connection.Send("DeleteGroup\nSuccess\n\n");
                }
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onGiveGroupAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("gid") && obj.ContainsKey("targetid"))
            {
                long gid = obj.Value<long>("gid");
                long targetid = obj.Value<long>("targetid");
                if (groups[gid].owner != id)
                {
                    await SendErrorAsync(connection, Errors.Group_NoPermission);
                    return;
                }
                else
                {
                    await SendMessageAsync(CreateMessage(0, gid, true, string.Format($"群[{{0}}]({{1}})已被群主转让给[{{3}}]({{4}})", users[gid].username, gid, users[targetid].username, targetid), 0));
                    groups[gid].owner = targetid;
                    groups[gid].admins.Add(id);
                    await connection.Send("GiveGrooup\nSuccess\n\n");
                }
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onSetGroupAdminAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("gid") && obj.ContainsKey("admins"))
            {
                long gid = obj.Value<long>("gid");
                if (groups[gid].owner != id)
                {
                    await SendErrorAsync(connection, Errors.Group_NoPermission);
                    return;
                }
                else
                {
                    List<long> admins = obj.Value<List<long>>("admins");
                    groups[gid].admins = admins;
                    await connection.Send("SetGroupAdmin\nSuccess\n\n");
                }
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onJoinGroupAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("gid"))
            {
                long gid = obj.Value<long>("gid");
                if (groups[gid].users.Contains(id))
                {
                    await SendErrorAsync(connection, Errors.JoinGroup_AlreadyJoined);
                    return;
                }
                else
                {
                    switch (groups[gid].joinWay)
                    {
                        case Group.JoinWay.All:
                            groups[gid].users.Add(id);
                            foreach (long u in groups[gid].admins)
                            {
                                await SendMessageAsync(CreateMessage(0, u, false, string.Format($"[{{0}}]({{1}})已加入群[{{3}}]({{4}})", users[id].username, id, groups[gid].name, gid), 0));
                            }
                            await SendMessageAsync(CreateMessage(0, gid, true, string.Format("[{0}]({1})已加入群", users[id].username, id), 0));
                            users[id].groups.Add(gid);
                            await connection.Send("JoinGroup\nSuccess\n\n");
                            break;
                        case Group.JoinWay.JoinRequset:
                            if (groups[gid].joinQuestion == "")
                            {
                                foreach (long u in groups[gid].admins)
                                {
                                    await SendMessageAsync(CreateMessage(0, u, false, string.Format($"[{{0}}]({{1}})申请加入群[{{3}}]({{4}})", users[id].username, id, groups[gid].name, gid), 0));
                                }
                            }
                            else
                            {
                                if (obj.ContainsKey("answer"))
                                {
                                    foreach (long u in groups[gid].admins)
                                    {
                                        await SendMessageAsync(CreateMessage(0, u, false, string.Format($"[{{0}}]({{1}})申请加入群[{{3}}]({{4}})，验证问题：{{5}}。验证消息：{{6}}", users[id].username, id, groups[gid].name, gid, groups[gid].joinQuestion, obj.Value<string>("answer")), 0));
                                    }
                                }
                                else
                                {
                                    await SendErrorAsync(connection, Errors.JoinGroup_AnswerRequired);
                                    return;
                                }
                            }
                            await connection.Send("JoinGroup\nWaitForAllow\n\n");
                            break;
                        case Group.JoinWay.AnswerQuestion:
                            if (obj.ContainsKey("answer"))
                            {
                                if (obj.Value<string>("answer") == groups[gid].joinAnswer)
                                {
                                    groups[gid].users.Add(id);
                                    foreach (long u in groups[gid].admins)
                                    {
                                        await SendMessageAsync(CreateMessage(0, u, false, string.Format($"[{{0}}]({{1}})已加入群[{{3}}]({{4}})", users[id].username, id, groups[gid].name, gid), 0));
                                    }
                                    await SendMessageAsync(CreateMessage(0, gid, true, string.Format("[{0}]({1})已加入群", users[id].username, id), 0));
                                    users[id].groups.Add(gid);
                                    await connection.Send("JoinGroup\nSuccess\n\n");
                                }
                            }
                            else
                            {
                                await SendErrorAsync(connection, Errors.JoinGroup_AnswerRequired);
                                return;
                            }
                            break;
                        case Group.JoinWay.Invite:
                            await SendMessageAsync(CreateMessage(0, id, false, string.Format("群[{0}]({1})只能被邀请加入", groups[gid].name, gid), 0));
                            break;
                        case Group.JoinWay.None:
                            await SendMessageAsync(CreateMessage(0, id, false, string.Format("群[{0}]({1})不允许加入", groups[gid].name, gid), 0));
                            break;
                    }
                }
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onLeaveGroupAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("gid"))
            {
                long gid = obj.Value<long>("gid");
                if (groups[gid].users.Contains(id) || id == groups[gid].owner)
                {
                    users[id].groups.Remove(gid);
                    groups[gid].users.Remove(gid);
                    groups[gid].admins.Remove(id);
                    foreach (long u in groups[gid].admins)
                    {
                        await SendMessageAsync(CreateMessage(0, u, false, string.Format($"[{{0}}]({{1}})已退出群[{{3}}]({{4}})", users[id].username, id, groups[gid].name, gid), 0));
                    }
                }
                else
                {
                    await SendErrorAsync(connection, Errors.Group_NoPermission);
                    return;
                }
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onAllowJoinGroupRequestAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("gid") && obj.ContainsKey("target"))
            {
                long gid = obj.Value<long>("gid");
                long target = obj.Value<long>("target");
                if (!users.ContainsKey(target))
                {
                    await SendErrorAsync(connection, Errors.Group_NoSuchUser);
                    return;
                }
                if (!groups[gid].users.Contains(target))
                {
                    groups[gid].users.Add(target);
                }
                await connection.Send("AllowJoinGroupRequest\nSuccess\n\n");
            }
        }
        protected virtual async Task onSetGroupInfoAsync(long id, string msg, IWebSocketConnection connection)
        {
            Group group = JsonConvert.DeserializeObject<Group>(msg);
            if (groups[group.id].admins.Contains(id) || groups[group.id].owner == id)
            {
                groups[group.id].name = group.name;
                groups[group.id].joinAnswer = group.joinAnswer;
                groups[group.id].joinWay = group.joinWay;
                groups[group.id].joinQuestion = group.joinQuestion;
                await connection.Send("SetGroupInfo\nSuccess\n\n");
            }
            else
            {
                await SendErrorAsync(connection, Errors.Group_NoPermission);
            }
        }
        protected virtual async Task onKickGroupMemberAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject obj = JObject.Parse(msg);
            if (obj.ContainsKey("gid") && obj.ContainsKey("target"))
            {
                long gid = obj.Value<long>("gid");
                long target = obj.Value<long>("target");
                if (groups[gid].owner != id && (!groups[gid].admins.Contains(id)))
                {
                    await SendErrorAsync(connection, Errors.Group_NoPermission);
                    return;
                }
                if (groups[gid].admins.Contains(target) || groups[gid].owner == target)
                {
                    await SendErrorAsync(connection, Errors.Group_NoPermission);
                    return;
                }
                if (groups[gid].users.Remove(target))
                {
                    groups[gid].admins.Remove(target);
                    await connection.Send("KickGroupMember\nSuccess\n\n");
                }
                else
                {
                    await SendErrorAsync(connection, Errors.Group_NoSuchUser);
                    return;
                }
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
            }
        }
        protected virtual async Task onRegisterAsync(string[] header, string msg, IWebSocketConnection connection)
        {
            if (msg == "")
            {
                try
                {
                    foreach (var i in users)
                    {
                        if (i.Value.email == header[0])
                        {
                            await SendErrorAsync(connection, Errors.Register_EmailAlreadyExist);
                            return;
                        }
                    }
                    if (!registerTokens.ContainsKey(connection))
                    {
                        await Task.Run(() =>
                        {
                            registerTokens.Add(connection, new KeyValuePair<long, string>(random.Next() % 1000000, header[0]));
                            SendEmail(header[0], "ICU邮箱验证",
                                string.Format("验证码是{0}，请勿告诉他人", registerTokens[connection].Key));
                            Task.Run(async () =>
                            {
                                await Task.Delay((int)(config.token_keep_time * 1000));
                                if (connection.IsAvailable)
                                {
                                    registerTokens.Remove(connection);
                                }
                            });
                        });
                        await connection.Send("Register\nSendEmail\n\n");
                    }
                    else
                    {
                        await connection.Send("Register\nEmailSent\n\n");
                    }
                }
                catch (Exception ex)
                {
                    serverLogger.Error("{}", ex);
                }
            }
            else
            {
                if (Convert.ToInt64(header[0]) != registerTokens[connection].Key)
                {
                    await SendErrorAsync(connection, Errors.Register_WrongEmailToken);
                    return;
                }
                JObject obj = JObject.Parse(msg);
                User user = JsonConvert.DeserializeObject<User>(msg);
                int id;
                for (id = random.Next(); users.ContainsKey(id); id = random.Next()) { }
                user.id = id;
                user.email = registerTokens[connection].Value;
                user.isOnlune = true;
                user.password = SHA256(user.password);
                users.Add(user.id, user);
                registerTokens.Remove(connection);
                ID2Connection.Add(user.id, connection);
                connection2ID.Add(connection, user.id);
                File.WriteAllText(@"usermessage\" + id, "");
                await connection.Send("Register\nSuccess\n\n" + user.id);
            }
        }
        protected virtual async Task onClientSendMessageAsync(long id, string msg, IWebSocketConnection connection)
        {
            JObject jMsg = JObject.Parse(msg);
            if (jMsg.ContainsKey("isGroup") &&
                jMsg.ContainsKey("isReply") &&
                jMsg.ContainsKey("replyId") &&
                jMsg.ContainsKey("target") &&
                jMsg.ContainsKey("msg"))
            {
                Message message = JsonConvert.DeserializeObject<Message>(msg);
                if (message.isGroup)
                {
                    if (!groups[message.target].users.Contains(id))
                    {
                        await SendErrorAsync(connection, Errors.Group_NoPermission);
                        return;
                    }
                }
                else
                {
                    if (!users.ContainsKey(message.target))
                    {
                        await SendErrorAsync(connection, Errors.Send_UserNotExist);
                        return;
                    }
                }
                message.messageId = GetMessageId();
                message.sender = id;
                message.sendTime = DateTime.Now;
                await SendMessageAsync(message);
                onUserSendMessageEvent(new UserSendMessageEventArgs { message = message });
                if (message.isGroup)
                {
                    onUserSendGroupMessageEvent(new UserSendGroupMessageEventArgs { message = message });
                }
                else
                {
                    onUserSendUserMessageEvent(new UserSendUserMessageEventArgs { message = message });
                }
                await connection.Send("Send\nSuccess\n\n");
            }
            else
            {
                await SendErrorAsync(connection, Errors.Other_UnknownMethod);
                return;
            }
        }
        protected virtual async Task onLogoutAsync(long id, string msg, IWebSocketConnection connection)
        {
            ID2Connection.Remove(id);
            connection2ID.Remove(connection);
            users[id].isOnlune = false;
            onUserLogout(new UserLogoutEventArgs { Id = id, cause = LogoutCause.Logout });
            await connection.Send("Logout\nSuccess\n\n");
            connection.Close();
        }
        protected virtual async Task onLoginAsync(string[] header, string msg, IWebSocketConnection connection)
        {
            long id = Convert.ToInt64(header[0]);
            if (!users.ContainsKey(id))
            {
                await SendErrorAsync(connection, Errors.Login_NoSuchUser);
                return;
            }
            else if (users[id].password != SHA256(header[1]))
            {
                await SendErrorAsync(connection, Errors.Login_WrongPassword);
                onUserLogin(new UserLoginEventArgs { Id = id, PasswordRight = false });
                return;
            }
            else if (Banlist.Contains(id))
            {
                await SendErrorAsync(connection, Errors.Login_Banned);
                return;
            }
            else if (config.join_key != null &&
                (header.Length < 3 || header[2] != config.join_key))
            {
                await SendErrorAsync(connection, Errors.Login_WrongJoinKey);
            }
            else
            {
                if (users[id].isOnlune)
                {
                    if (connection2ID.ContainsKey(connection))
                    {
                        await connection.Send("Login\nLoggedIn\n\n");
                        return;
                    }
                    connection2ID.Remove(ID2Connection[id]);
                    await SendErrorAsync(ID2Connection[id], Errors.Logout_AnotherLogin);
                    onUserLogout(new UserLogoutEventArgs { Id = id, cause = LogoutCause.AnotherLogin });
                    ID2Connection[id].Close();
                }
                users[id].isOnlune = true;
                ID2Connection[id] = connection;
                connection2ID.Add(connection, id);
                onUserLogin(new UserLoginEventArgs { Id = id, PasswordRight = true });
                await connection.Send("Login\nSuccess\n\n");
            }
        }
        ~Server()
        {
            Stop();
        }
        public bool Ban(long id)
        {
            if (!users.ContainsKey(id) || Banlist.Contains(id))
            {
                return false;
            }
            if (users[id].isOnlune)
            {
                users[id].isOnlune = false;
                connection2ID.Remove(ID2Connection[id]);
                Task.Run(async () =>
                {
                    await SendErrorAsync(ID2Connection[id], Errors.Logout_Banned);
                    ID2Connection[id].Close();
                    ID2Connection.Remove(id);
                });
            }
            Banlist.Add(id);
            return true;
        }
        public bool UnBan(long id)
        {
            return Banlist.Remove(id);
        }
        public void Stop()
        {
            IsRunning = false;
            serverLogger.Info("Disabling plugins...");
            onPluginDisable(new PluginDisableEventArgs());
            foreach (var item in Plugins.Values)
            {
                if (item.GetType().GetTypeInfo().IsCollectible)
                {
                    System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(item.GetType().Assembly).Unload();
                }
            }
            Plugins.Clear();
            Assembly.GetExecutingAssembly();
            serverLogger.Info("Done.");
            serverLogger.Info("Stopping server...");
            foreach (var i in connection2ID)
            {
                SendErrorAsync(i.Key, Errors.Logout_ServerClosed).Wait();
                i.Key.Close();
                users[i.Value].isOnlune = false;
            }
            webSocketServer.Dispose();
            Save();
        }
        public void SaveConfig(bool log = false)
        {
            if (log) serverLogger.Info("Saving server config...");
            File.WriteAllText(@"config\server.json", JsonConvert.SerializeObject(config, Formatting.Indented));
        }
        public void Save(bool log = true)
        {
            JObject jo;
            if (log) serverLogger.Info("Saving common data...");
            jo = new JObject
            {
                { "mid", lastMessageID },
            };
            File.WriteAllText(@"data\common.json", jo.ToString());
            if (log) serverLogger.Info("Saving banlist...");
            File.WriteAllText(@"data\banlist.json", JsonConvert.SerializeObject(Banlist));
            if (log) serverLogger.Info("Saving groups...");
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append('[');
            foreach (var i in groups)
            {
                stringBuilder.Append(JsonConvert.SerializeObject(i.Value));
                stringBuilder.Append(',');
            }
            stringBuilder.Append(']');
            File.WriteAllText(@"data\group.json", stringBuilder.ToString());
            if (log) serverLogger.Info("Saving users...");
            stringBuilder.Clear();
            stringBuilder.Append('[');
            foreach (var i in users)
            {
                i.Value.isOnlune = false;
                stringBuilder.Append(JsonConvert.SerializeObject(i.Value));
                stringBuilder.Append(',');
            }
            stringBuilder.Append(']');
            File.WriteAllText(@"data\user.json", stringBuilder.ToString());
        }
        public void Test()
        {
            Console.WriteLine("Develop version v 1.0.1");
            Task.Run(async () =>
            {
                while (true)
                {
                    NullCheck();
                    await Task.Delay(30000);
                }
            });

        }

        public override Message SendSystemMessage(long to, string msg)
        {
            Message message = CreateMessage(0, to, false, msg, 0);
            SendMessageAsync(message).Wait();
            return message;
        }

        public override void LoadPluginByFullName(string fullName)
        {
            LoadComDllPlugin(fullName);
        }
    }
}
